<?php

namespace App\Kuafu\Pay;

use App\Kuafu\Pay\Channel\AliPay\AliPay;
use App\Kuafu\Pay\Channel\Cash\CashPay;
use App\Kuafu\Pay\Channel\CustomPay\CustomPay;
use App\Kuafu\Pay\Channel\FuYou\FuYouPay;
use App\Kuafu\Pay\Channel\FuYou\FuYouPayMock;
use App\Kuafu\Pay\Channel\Mybank\MybankPay;
use App\Kuafu\Pay\Channel\MybankCashier\MybankCashierPay;
use App\Kuafu\Pay\Channel\WxPay\WxPay;
use App\Kuafu\Pay\Enum\PayCodeEnum;
use App\Kuafu\Pay\Exceptions\PayException;
use App\Kuafu\Pay\Model\PayStreamModel;
use App\Kuafu\Pay\Service\PayConfigService;
use Illuminate\Support\Facades\Redis;

abstract class BasePay
{
    public const API_RETURN_SUCCESS = 'SUCCESS';

    private static  $ins;
    private static  $insExpiredAt;
    protected array $payConfig;
    protected int   $sysType;
    protected int   $userId;
    protected int   $childPayChannel;
    protected int $shopId;

    // 启动方法
    public static function instance(int $payChannel, int $shopId, int $sysType, int $forceCompany = 0): self
    {
        switch ($payChannel) {
            case PayStreamModel::PAY_CHANNEL_ALI:
                $className = AliPay::class;
                break;
            case PayStreamModel::PAY_CHANNEL_WX:
                $className = WxPay::class;
                break;
            case PayStreamModel::PAY_CHANNEL_FY:
                if (!$shopId) {
                    throw new PayException(PayCodeEnum::ERR_PAY_FY_SHOP_ID_EMPTY);
                }

                if (config('app.pressure')) {
                    $className = FuYouPayMock::class;
                } else {
                    $className = FuYouPay::class;
                }

                break;
            case PayStreamModel::PAY_CHANNEL_CASH:
                $className = CashPay::class;
                break;
            case PayStreamModel::PAY_CHANNEL_MYBANK_PAY:
                $className = MybankPay::class;
                break;
            case PayStreamModel::PAY_CHANNEL_MYBANK_CASHIER_PAY:
                $className = MybankCashierPay::class;
                break;
            case $payChannel > PayStreamModel::PAY_CHANNEL_CUSTOM_PAYMENT:
                $className = CustomPay::class;
                break;
            default:
                throw new PayException(PayCodeEnum::ERR_PAY_ERROR_PAY_CHANNEL);
        }

        $insKey = "{$className}:{$shopId}:{$sysType}:{$forceCompany}";

        // 享元10分钟过期
        if (isset(static::$ins[$insKey]) && static::$insExpiredAt[$insKey] - time() > 0) {
            return static::$ins[$insKey];
        }

        static::$ins[$insKey]          = new $className($shopId, $sysType, $payChannel, $forceCompany);
        static::$insExpiredAt[$insKey] = time() + 600;
        return static::$ins[$insKey];
    }

    protected $outTradeNo = '';

    protected function __construct(int $shopId, int $sysType, int $payChannel, int $forceCompany)
    {
        $this->sysType = $sysType;
        $this->shopId = $shopId;

        $this->payConfig = (new PayConfigService($payChannel, $sysType, $shopId, $forceCompany))->getPayConfig();

        $this->initConfig($shopId, $sysType);
    }

    public function setUserId(int $userId): self
    {
        $this->userId = $userId;

        return $this;
    }

    public function setChildPayChannel(int $childPayChannel): self
    {
        $this->childPayChannel = $childPayChannel;

        return $this;
    }

    // 初始化配置信息
    abstract protected function initConfig(int $shopId, int $sysType);

    /**
     * 当面付 - 被扫支付
     * @param string $subject 商品名称
     * @param string $outTradeNo 万皮思订单号
     * @param int $amount 金额 1=1分
     * @param string $authCode 用户的支付码
     * @return mixed
     */
    abstract public function faceToFacePay(string $subject, int $amount, string $authCode);

    /**
     * 关闭订单
     * @param string $outTradeNo
     * @return array
     */
    abstract public function closePay(string $outTradeNo): array;

    /**
     * 退款
     * @param string $outTradeNo 原支付订单号
     * @param int $refundAmount 退款金额 1=1分
     * @return mixed
     */
    abstract public function refund(string $outTradeNo, int $refundAmount);

    // 查询订单
    abstract public function query(string $outTradeNo);

    // 退款订单查询
    abstract public function refundQuery(string $outTradeNo, string $outRequestNo);

    /**
     * 对账单
     * @param string $date
     * @return mixed
     */
    abstract public function downloadBill(string $date);

    // 多端统一的接口返回字段处理
    abstract protected function commonReturn($result): array;

    // 生成新的唯一订单号
    protected function createUniqueTradeNo(): string
    {
        $autoIncr = Redis::incr('CREATE_OUT_TRADENO', 1);
        if ($autoIncr > 900000) {
            Redis::set('CREATE_OUT_TRADENO', 1);
        }

        $autoIncrStr = sprintf('%06s', $autoIncr);

        // 14+6+4=24长度
        return $this->outTradeNo = date('YmdHis') . $autoIncrStr . rand(1000, 9999);
    }

    // 获取订单号
    public function getOutTradeNo(bool $new = false)
    {
        if ($new) {
            return $this->createUniqueTradeNo();
        }

        return $this->outTradeNo ?: $this->createUniqueTradeNo();
    }

    /**
     * 接口返回通用处理
     * @param bool $status
     * @param string $code
     * @param string $msg
     * @param array $oriData
     * @return array
     */
    protected function returnFormatData(bool $status, string $code, string $msg, array $oriData): array
    {
        return [
            'status'  => $status, // true: 接口成功 false: 接口失败
            'code'    => $code, // 错误码
            'msg'     => $msg,  // 错误信息
            'data'    => [],   // 多种支付渠道合并后的，固定通用返回字段
            'oriData' => $oriData, //
        ];
    }

    /**
     * 通过authCode检测payChannel
     * @param string $authCode
     * @return int
     * @throws PayException
     */
    public static function checkAuthCode(string $authCode): int
    {
        $prefix = substr($authCode, 0, 2);
        if ($prefix >= 25 && $prefix <= 30) {
            return PayStreamModel::PAY_CHANNEL_CHILD_ALI;
        }

        if ($prefix >= 10 && $prefix <= 19) {
            return PayStreamModel::PAY_CHANNEL_CHILD_WX;
        }

        if ($prefix == 62) {
            return PayStreamModel::PAY_CHANNEL_CHILD_UNION_PAY;
        }

        throw new PayException(PayCodeEnum::ERR_AUTH_CODE);
    }

    // TODO 支付成功通知

    // TODO 退款成功通知

    /**
     * 判断浏览器是支付宝、微信
     * @return int
     */
    public static function checkBrowser(): int
    {
        if (strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') == true) {
            return PayStreamModel::PAY_CHANNEL_CHILD_WX;
        } else {
            if (strpos($_SERVER['HTTP_USER_AGENT'], 'AlipayClient') == true) {
                return PayStreamModel::PAY_CHANNEL_CHILD_ALI;
            } else {
                return PayStreamModel::PAY_CHANNEL_CHILD_NULL;
            }
        }
    }

    public function getPayConfig(): array
    {
        return $this->payConfig;
    }
}
